// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/* BusinessObjectDataSource.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System.Collections.Generic;
using System.Collections;

using AM.Reporting.Utils;

#endregion

#nullable enable

namespace AM.Reporting.Data;

/// <summary>
/// Represents a datasource based on business object of <b>IEnumerable</b> type.
/// </summary>
/// <remarks>
/// Do not use this class directly. To register a business object, use the
/// <b>Report.RegisterData</b> method.
/// </remarks>
public class BusinessObjectDataSource
    : DataSourceBase
{
    #region Events

    /// <summary>
    /// Occurs when AM.Reporting engine loads data source with data from a business object.
    /// </summary>
    /// <remarks>
    /// Use this event if you want to implement load-on-demand. Event handler must load the data into
    /// your business object.
    /// </remarks>
    public event LoadBusinessObjectEventHandler? LoadBusinessObject;

    #endregion

    #region Private methods

    private void LoadData
        (
            IEnumerable? enumerable,
            IList rows
        )
    {
        if (enumerable is null)
        {
            return;
        }

        OnLoadBusinessObject();

        var enumerator = enumerable.GetEnumerator();
        while (enumerator.MoveNext())
        {
            rows.Add (enumerator.Current);
        }
    }

    private void OnLoadBusinessObject()
    {
        LoadBusinessObject?.Invoke (this, new LoadBusinessObjectEventArgs (Value));
    }

    #endregion

    #region Protected methods

    /// <inheritdoc cref="DataSourceBase.GetValue(string)"/>
    protected override object? GetValue
        (
            string alias
        )
    {
        var colAliases = alias.Split ('.');
        Column? column = this;

        foreach (var colAlias in colAliases)
        {
            column = column.Columns.FindByAlias (colAlias);
            if (column is null)
            {
                return null;
            }
        }

        return GetValue (column);
    }

    /// <inheritdoc cref="DataSourceBase.GetValue(AM.Reporting.Data.Column)"/>
    protected override object? GetValue
        (
            Column? column
        )
    {
        if (column is null)
        {
            return null;
        }

        // check if column is a list value
        if (column.PropDescriptor is null && column.PropertyName == "Value")
        {
            return CurrentRow;
        }

        // get nested columns in right order
        var columns = new List<Column>();
        while (column != this)
        {
            columns.Insert (0, column!);
            column = column!.Parent as Column;
        }

        var obj = CurrentRow;
        foreach (var c in columns)
        {
            if (obj is null)
            {
                return null;
            }

            obj = c.PropDescriptor!.GetValue (obj);
        }

        return obj;
    }

    #endregion

    #region Public methods

    /// <inheritdoc cref="DataSourceBase.InitSchema"/>
    public override void InitSchema()
    {
        // do nothing; the schema was initialized when we register a business object.
    }

    /// <inheritdoc cref="DataSourceBase.LoadData"/>
    public override void LoadData
        (
            IList rows
        )
    {
        rows.Clear();

        // custom load data via Load event
        OnLoad();

        var parent = ParentDataSource;
        var isMasterDetail = parent is { RowCount: > 0 };

        if (isMasterDetail)
        {
            LoadData (Value as IEnumerable, rows);
        }
        else
        {
            // ensure that parent is loaded
            if (parent is { InternalRows.Count: 0 })
            {
                parent.Init();
            }

            if (parent is null)
            {
                // this is a root business object, its Reference property contains IEnumerable.
                LoadData (Reference as IEnumerable, rows);
            }
            else
            {
                // enumerate parent rows to fill this data source completely
                parent.First();
                while (parent.HasMoreRows)
                {
                    LoadData (Value as IEnumerable, rows);
                    parent.Next();
                }

                // bug fix - two-pass report shows empty data
                parent.ClearData();
            }
        }
    }

    /// <inheritdoc/>
    public override void Deserialize (ReportReader reader)
    {
        base.Deserialize (reader);

        // compatibility with old reports: try to use last part of ReferenceName as a value for PropName
        if (!string.IsNullOrEmpty (ReferenceName) && ReferenceName.Contains ("."))
        {
            var names = ReferenceName.Split ('.');
            PropertyName = names[^1];
            ReferenceName = "";
        }

        // gather all nested datasource names (PropName properties)
        List<string> dataSourceNames = new List<string>();
        foreach (Column column in Columns)
        {
            if (column is BusinessObjectDataSource)
            {
                dataSourceNames.Add (column.PropertyName);
            }
        }

        // delete simple columns that have the same name as a datasource. In old version,
        // there was an invisible column used to support BO infrastructure
        for (var i = 0; i < Columns.Count; i++)
        {
            var column = Columns[i];
            if (column is not BusinessObjectDataSource && dataSourceNames.Contains (column!.PropertyName))
            {
                column.Dispose();
                i--;
            }
        }
    }

    #endregion
}
